#pragma once
#include <brpc/channel.h>
#include <string>
#include <vector>
#include <unordered_map>
#include <unordered_set>
#include "main.pb.h"
#include "spdlogger.hpp"
#include "etcd.hpp"

/*
    对brpc框架中信道二次封装，实现服务信道类和服务信道管理类：与服务注册发现中心结合
    封装思想：
    客户端rpc调用微服务需要信道来完成通信，信道需要服务的ip和port，而这些微服务上线和下线都需要经过服务注册发现中心
    这也就意味着信道的ip+port是不能提前确定好的，需要根据发现中心动态变化，并且可能多个节点都提供相同的微服务
    那么一个微服务就对应多个节点信息，也就对应多个信道。同时也要对多个不同的服务管理，为了方便管理对信道二次封装
    1、服务信道类：一个服务对应多个信道，实现添加、移除、选择信道方法
    2、服务管理类：对服务信道类统一管理，相当于管理多个不同的微服务，实现服务上线、下线管理，作为etcd的回调
*/

namespace ns_channel
{
    // 服务信道类
    class ServiceChannel
    {
    public:
        ServiceChannel(const std::string &service_name)
            : _service_name(service_name), _index(0)
        {
        }

        // 添加信道：当前服务有节点上线，新增一个信道
        bool append(const std::string &host)
        {
            // 1、定义信道，并初始化
            brpc::ChannelOptions options;
            options.connect_timeout_ms = -1;
            options.timeout_ms = -1;
            options.max_retry = 3;
            options.protocol = "baidu_std";
            std::shared_ptr<brpc::Channel> channel = std::make_shared<brpc::Channel>();
            int ret = channel->Init(host.c_str(), &options);
            if (ret == -1)
            {
                LOG_ERROR("{}服务初始化{}信道失败！", _service_name, host);
                return false;
            }

            // 2、添加信道到集合，并添加节点和信道关系
            std::unique_lock<std::mutex> lock(_mutex);
            _channels.push_back(channel);
            _host_to_channel[host] = channel;
            return true;
        }

        // 移除信道：节点下线，将节点对应的信道移除
        bool remove(const std::string &host)
        {
            std::unique_lock<std::mutex> lock(_mutex);
            // 1、根据节点找到对应信道
            auto it_host = _host_to_channel.find(host);
            if (it_host == _host_to_channel.end())
            {
                LOG_WARN("没有匹配到{}节点对应的信道！", host);
                return false;
            }

            // 2、从集合中移除信道
            auto it_channel = _channels.begin();
            for (; it_channel != _channels.end(); ++it_channel)
                if (*it_channel == it_host->second)
                    break;
            if (it_channel == _channels.end())
            {
                LOG_WARN("在集合不存在节点{}对应的信道！", host);
                return false;
            }

            // 3、移除
            _host_to_channel.erase(it_host); // 移除节点与信道对应关系
            _channels.erase(it_channel);     // 从结合中移除信道
            return true;
        }

        // RR轮询选择一个信道
        std::shared_ptr<brpc::Channel> choose()
        {
            std::unique_lock<std::mutex> lock(_mutex);
            if (_channels.size())
            {
                LOG_INFO("提供{}服务的所有节点下线！", _service_name);
                return std::shared_ptr<brpc::Channel>();
            }
            int idx = _index;
            _index = (_index + 1) % _channels.size();
            return _channels[idx];
        }

    private:
        std::string _service_name;                                                        // 服务名称
        std::vector<std::shared_ptr<brpc::Channel>> _channels;                            // 当前服务对应的信道集合
        std::unordered_map<std::string, std::shared_ptr<brpc::Channel>> _host_to_channel; // 节点地址与信道的对应关系
        int _index;                                                                       // RR轮询下标
        std::mutex _mutex;
    };

    // 服务信道管理类
    class ServiceManager
    {
    public:
        // 声明要管理的服务，只有被声明的服务，服务上下线才需要管理
        void declare(const std::string &service_name)
        {
            std::unique_lock<std::mutex> lock(_mutex);
            _declare_services.insert(service_name);
        }

        // 上线回调，作为服务发现中心的服务上线回调函数
        void online(const std::string &service_name, const std::string &host)
        {
            std::shared_ptr<ServiceChannel> service_channel;
            {
                std::unique_lock<std::mutex> lock(_mutex);
                // 1、判断服务是否已声明
                auto it_declare = _declare_services.find(service_name);
                if (it_declare == _declare_services.end())
                {
                    LOG_DEBUG("{}提供的服务{}上线，但是未声明", host, service_name);
                    return;
                }

                // 2、将服务添加到服务信道类管理集合
                auto it_service = _services.find(service_name); // 查找对应的服务
                if (it_service == _services.end())
                {
                    // 服务还没有被管理，创建服务信道类并管理起来
                    service_channel = std::make_shared<ServiceChannel>(service_name); // 创建服务信道类
                    _services[service_name] = service_channel;                        // 添加到管理集合
                }
                else
                {
                    // 已经存在，直接获取
                    service_channel = it_service->second;
                }
            } // 这里添加作用域是因为不想让本题中的锁和信道中的锁共同加锁

            // 2、将提供服务的节点添加到对应的服务信道集合中
            service_channel->append(host);
            LOG_DEBUG("{}服务上线节点{}", service_name, host);
        }

        // 下线回调，作为服务发现中心的服务下线回调函数
        void offline(const std::string &service_name, const std::string &host)
        {
            std::shared_ptr<ServiceChannel> service_channel;
            {
                std::unique_lock<std::mutex> lock(_mutex);
                // 1、判断服务是否已声明
                auto it_declare = _declare_services.find(service_name);
                if (it_declare == _declare_services.end())
                {
                    LOG_DEBUG("{}提供的服务{}下线，但是未声明", host, service_name);
                    return;
                }

                // 2、在管理集合中找到服务的服务信道类
                auto it_service = _services.find(service_name);
                if (it_service == _services.end())
                {
                    LOG_WARN("{}服务未找到", service_name);
                    return;
                }
                service_channel = it_service->second;
            } // 加作用域不想两次加锁

            // 2、在服务信道类中移除服务下线的节点
            service_channel->remove(host);
            LOG_DEBUG("{}服务下线{}节点", service_name, host);
        }

    private:
        std::unordered_set<std::string> _declare_services;                          // 已声明的服务集合，被声明的服务才需要管理
        std::unordered_map<std::string, std::shared_ptr<ServiceChannel>> _services; // 服务信道类集合
        std::mutex _mutex;
    };
}
